/*
 * File: Scene.cs
 * Author: Gourav G
 * Created: April 2011
 * No Copyright
 */

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using _22yards.Core;
using System.Diagnostics;

namespace _22yards._2D
{
    /// <summary>
    /// The SceneEngine manages Scenes. It maintains a stack of scenes, calls their Update and Draw
    /// methods at the required times and automatically routes input to the topmost active scene.
    /// </summary>
    public class SceneEngine : DrawableGameComponent
    {
        #region Fields
        List<Scene> scenes = new List<Scene>();
        List<Scene> scenesToUpdate = new List<Scene>();

        InputEngine input = new InputEngine();

        SpriteBatch spriteBatch;
        //SpriteFont font;
        Texture2D blankTexture;

        bool isInitialized;

        #endregion

        #region Properties


        /// <summary>
        /// A default SpriteBatch shared by all the screens. This saves
        /// each screen having to bother creating their own local instance.
        /// </summary>
        public SpriteBatch SpriteBatch
        {
            get { return spriteBatch; }
        }


        /// <summary>
        /// A default txtnfont engine shared by all the screens. This saves
        /// each screen having to bother loading their own local copy.
        /// </summary>
        public TextNFontEngine TxtNFont { get; set; }


        /// <summary>
        /// If true, the manager prints out a list of all the screens
        /// each time it is updated. This can be useful for making sure
        /// everything is being added and removed at the right times.
        /// </summary>
        public bool TraceEnabled { get; set; }


        #endregion

        #region Initialization


        /// <summary>
        /// Constructs a new scene engine.
        /// </summary>
        public SceneEngine(Game game, TextNFontEngine _txtNFont)
            : base(game)
        {
            TxtNFont = _txtNFont;
        }


        /// <summary>
        /// Initializes the scene engine.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            isInitialized = true;
        }


        /// <summary>
        /// Load your graphics content.
        /// </summary>
        protected override void LoadContent()
        {
            // Load content belonging to the scene engine.
            ContentManager content = Game.Content;
            spriteBatch = new SpriteBatch(GraphicsDevice);
            blankTexture = content.Load<Texture2D>("TwoDEE/Screens/blank");

            // Tell each of the scenes to load their content.
            foreach (Scene scene in scenes)
            {
                scene.LoadContent();
            }
        }


        /// <summary>
        /// Unload your graphics content.
        /// </summary>
        protected override void UnloadContent()
        {
            // Tell each of the scenes to unload their content.
            foreach (Scene scene in scenes)
            {
                scene.UnloadContent();
            }
        }


        #endregion

        #region Update and Draw


        /// <summary>
        /// Allows each screen to run logic.
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            // Read the keyboard and gamepad.
            input.Update(gameTime);

            // Make a copy of the master screen list, to avoid confusion if
            // the process of updating one screen adds or removes others.
            scenesToUpdate.Clear();

            foreach (Scene scene in scenes)
                scenesToUpdate.Add(scene);

            bool otherScreenHasFocus = !Game.IsActive;
            bool coveredByOtherScreen = false;

            // Loop as long as there are scene waiting to be updated.
            while (scenesToUpdate.Count > 0)
            {
                // Pop the topmost scene off the waiting list.
                Scene scene = scenesToUpdate[scenesToUpdate.Count - 1];

                scenesToUpdate.RemoveAt(scenesToUpdate.Count - 1);

                // Update the scene.
                scene.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

                if (scene.SceneState == enumSceneState.Transitioning ||
                    scene.SceneState == enumSceneState.IsActive)
                {
                    // If this is the first active scene we came across,
                    // give it a chance to handle input.
                    if (!otherScreenHasFocus)
                    {
                        scene.HandleInput(input);

                        otherScreenHasFocus = true;
                    }

                    // If this is an active non-popup, inform any subsequent
                    // scenes that they are covered by it.
                    if (!scene.IsPopup)
                        coveredByOtherScreen = true;
                }
            }

            // Print debug trace?
            if (TraceEnabled)
                TraceScenes();
        }


        /// <summary>
        /// Prints a list of all the scenes, for debugging.
        /// </summary>
        void TraceScenes()
        {
            List<string> sceneNames = new List<string>();

            foreach (Scene screen in scenes)
                sceneNames.Add(screen.GetType().Name);

            Debug.WriteLine(string.Join(", ", sceneNames.ToArray()));
        }


        /// <summary>
        /// Tells each scene to draw itself.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            foreach (Scene scene in scenes)
            {
                if (scene.SceneState == enumSceneState.InActive)
                    continue;

                scene.Draw(gameTime);
            }
        }


        #endregion

        #region Public Methods


        /// <summary>
        /// Adds a new scene to the screen manager.
        /// </summary>
        public void AddScene(Scene scene, PlayerIndex? controllingPlayer)
        {
            scene.ControllingPlayer = controllingPlayer;
            scene.sceneEngine = this;
            scene.IsExiting = false;

            // If we have a graphics device, tell the scene to load content.
            if (isInitialized)
            {
                scene.LoadContent();
            }

            scenes.Add(scene);

        }


        /// <summary>
        /// Removes a scene from the scene engine. You should normally
        /// use Scene.ExitScene instead of calling this directly, so
        /// the screen can gradually transition off rather than just being
        /// instantly removed.
        /// </summary>
        public void RemoveScene(Scene scene)
        {
            // If we have a graphics device, tell the scene to unload content.
            if (isInitialized)
            {
                scene.UnloadContent();
            }

            scenes.Remove(scene);
            scenesToUpdate.Remove(scene);

        }


        /// <summary>
        /// Expose an array holding all the scenes. We return a copy rather
        /// than the real master list, because screens should only ever be added
        /// or removed using the AddScene and RemoveScene methods.
        /// </summary>
        public Scene[] GetScenes()
        {
            return scenes.ToArray();
        }


        /// <summary>
        /// Helper draws a translucent black fullscreen sprite, used for fading
        /// screens in and out, and for darkening the background behind popups.
        /// </summary>
        public void FadeBackBufferToBlack(float alpha)
        {
            Viewport viewport = GraphicsDevice.Viewport;

            spriteBatch.Begin();

            spriteBatch.Draw(blankTexture,
                             new Rectangle(0, 0, viewport.Width, viewport.Height),
                             Color.Black * alpha);

            spriteBatch.End();
        }


        #endregion
    }

}
